/*-------------------------------------------------------------------------
 *
 * spin.c
 *	   硬件无关的自旋锁实现。
 *
 *
 * 对于具有测试和设置（TAS）指令的机器，s_lock.h/.c定义自旋锁实现。该文件仅包含使用PGSemaphores的自旋锁的存根实现。除非信号量的实现方式不涉及内核调用，否则这对于实际使用来说太慢了:-(
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/storage/lmgr/spin.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "storage/pg_sema.h"
#include "storage/shmem.h"
#include "storage/spin.h"


#ifndef HAVE_SPINLOCKS

/*
 * 没有 TAS，因此自旋锁实现为 PGSemaphores。
 */

#ifndef HAVE_ATOMICS
#define NUM_EMULATION_SEMAPHORES (NUM_SPINLOCK_SEMAPHORES + NUM_ATOMICS_SEMAPHORES)
#else
#define NUM_EMULATION_SEMAPHORES (NUM_SPINLOCK_SEMAPHORES)
#endif							/* DISABLE_ATOMICS */

PGSemaphore *SpinlockSemaArray;

#else							/* !HAVE_SPINLOCKS */

#define NUM_EMULATION_SEMAPHORES 0

#endif							/* HAVE_SPINLOCKS */

/*
 * 报告存储自旋锁支持所需的信号量的共享内存量。
 */
Size SpinlockSemaSize(void)
{
	return NUM_EMULATION_SEMAPHORES * sizeof(PGSemaphore);
}

/*
 * 报告支持自旋锁所需的信号量数量。
 */
int SpinlockSemas(void)
{
	return NUM_EMULATION_SEMAPHORES;
}

#ifndef HAVE_SPINLOCKS

/*******************************
 * 初始化自旋锁仿真。
 *
 * 这必须在 PGReserveSemaphores() 之后调用。
 ********************************/
void SpinlockSemaInit(void)
{
	PGSemaphore *fc_spinsemas;
	int			fc_nsemas = SpinlockSemas();
	int			fc_i;

	/*******************************
	 * 我们必须使用 ShmemAllocUnlocked()，因为保护
	 * ShmemAlloc() 的自旋锁显然还不能准备好。
 ********************************/
	fc_spinsemas = (PGSemaphore *) ShmemAllocUnlocked(SpinlockSemaSize());
	for (fc_i = 0; fc_i < fc_nsemas; ++fc_i)
		fc_spinsemas[fc_i] = PGSemaphoreCreate();
	SpinlockSemaArray = fc_spinsemas;
}

/*******************************
 * s_lock.h 硬件自旋锁仿真使用信号量
 *
 * 我们将所有自旋锁映射到 NUM_EMULATION_SEMAPHORES 个信号量上。
 * 将多个自旋锁映射到一个信号量上是可以的，因为
 * 任何进程不应该同时持有多个。我们只需要足够的信号量以避免
 * 由于此导致的过多额外争用。
 *
 * 只有一个例外，即在自旋锁内部嵌套模拟原子操作是允许的。
 * 为了避免自旋锁和原子使用相同信号量的危险，
 * 我们确保“正常”的自旋锁和由自旋锁支持的原子使用
 * 不同的信号量（请参见 s_init_lock_sema 的嵌套参数）。
 *
 * slock_t 在此实现中只是一个整数；它保存自旋锁
 * 编号，从 1 到 NUM_EMULATION_SEMAPHORES。我们故意确保 0
 * 不是有效值，以便使用此代码进行测试可以帮助查找
 * 自旋锁未初始化的失败。
 ********************************/

static inline void fc_s_check_valid(int fc_lockndx)
{
	if (unlikely(fc_lockndx <= 0 || fc_lockndx > NUM_EMULATION_SEMAPHORES))
		elog(ERROR, "invalid spinlock number: %d", fc_lockndx);
}

void s_init_lock_sema(volatile slock_t *fc_lock, bool fc_nested)
{
	static uint32 fc_counter = 0;
	uint32		fc_offset;
	uint32		fc_sema_total;
	uint32		fc_idx;

	if (fc_nested)
	{
		/*******************************
		 * 为了允许在自旋锁定部分内部嵌套原子，使用一个
		 * 不同的自旋锁。请参见上面的评论。
 ********************************/
		fc_offset = 1 + NUM_SPINLOCK_SEMAPHORES;
		fc_sema_total = NUM_ATOMICS_SEMAPHORES;
	}
	else
	{
		fc_offset = 1;
		fc_sema_total = NUM_SPINLOCK_SEMAPHORES;
	}

	fc_idx = (fc_counter++ % fc_sema_total) + fc_offset;

	/*******************************
		 * 再次检查我们是否正确执行了操作
 ********************************/
	fc_s_check_valid(fc_idx);

	*fc_lock = fc_idx;
}

void s_unlock_sema(volatile slock_t *fc_lock)
{
	int			fc_lockndx = *fc_lock;

	fc_s_check_valid(fc_lockndx);

	PGSemaphoreUnlock(SpinlockSemaArray[fc_lockndx - 1]);
}

bool s_lock_free_sema(volatile slock_t *fc_lock)
{
	/*******************************
		 * 我们当前并不使用 S_LOCK_FREE
 ********************************/
	elog(ERROR, "spin.c does not support S_LOCK_FREE()");
	return false;
}

int tas_sema(volatile slock_t *fc_lock)
{
	int			fc_lockndx = *fc_lock;

	fc_s_check_valid(fc_lockndx);

	/*******************************
		 * 注意 TAS 宏在 *成功* 时返回 0
 ********************************/
	return !PGSemaphoreTryLock(SpinlockSemaArray[fc_lockndx - 1]);
}

#endif							/* !HAVE_SPINLOCKS */
